package com.app.util;
import java.io.File;
import java.io.FileFilter;
import java.lang.annotation.Annotation;
import java.net.JarURLConnection;
import java.net.URL;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import javassist.ClassPool;
import javassist.CtClass;
import com.app.annotation.Table;
 
public class ClassUtil {
	
	/**
	 * 获取指定包下的所有类
	 * @param packageName
	 * @param isRecursive 是否递归查找
	 * @return
	 */
	public static Map<String,Class<?>> getClasses(String packageName, boolean isRecursive) {
		Map<String,Class<?>> classes = new HashMap<String,Class<?>>();
        try {
            Enumeration<URL> urls = Thread.currentThread().getContextClassLoader().getResources(packageName.replaceAll("\\.", "/"));
            while (urls.hasMoreElements()) {
                URL url = urls.nextElement();
                if (url != null) {
                    String protocol = url.getProtocol();
                    if (protocol.equals("file")) {
                        String packagePath = url.getPath();
                        addClass(classes, packagePath, packageName, isRecursive);
                    } else if (protocol.equals("jar")) {
                        JarURLConnection jarURLConnection = (JarURLConnection) url.openConnection();
                        JarFile jarFile = jarURLConnection.getJarFile();
                        Enumeration<JarEntry> jarEntries = jarFile.entries();
                        while (jarEntries.hasMoreElements()) {
                            JarEntry jarEntry = jarEntries.nextElement();
                            String jarEntryName = jarEntry.getName();
                            if (jarEntryName.endsWith(".class")) {
                                String className = jarEntryName.substring(0, jarEntryName.lastIndexOf(".")).replaceAll("/", ".");
                                if (isRecursive || className.substring(0, className.lastIndexOf(".")).equals(packageName)) {
                                	Class<?> clz = Class.forName(className);
                                    classes.put(clz.getName(), clz);
                                }
                            }
                        }
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return classes;
    }
 
	/**
	 * 获取指定包名下的所有类（可根据注解进行过滤）
	 * @param packageName
	 * @param annotationClass
	 * @return
	 */
    public static Map<String,Class<?>> getClassesByAnnotation(String packageName, Class<? extends Annotation> annotationClass) {
    	Map<String,Class<?>> classes = new HashMap<String,Class<?>>();
    	ClassPool pool = ClassPool.getDefault();
        try {
            Enumeration<URL> urls = Thread.currentThread().getContextClassLoader().getResources(packageName.replaceAll("\\.", "/"));
            while (urls.hasMoreElements()) {
                URL url = urls.nextElement();
                if (url != null) {
                    String protocol = url.getProtocol();
                    if (protocol.equals("file")) {
                        String packagePath = url.getPath();
                        addClassByAnnotation(classes, packagePath, packageName, annotationClass);
                    } else if (protocol.equals("jar")) {
                        JarURLConnection jarURLConnection = (JarURLConnection) url.openConnection();
                        JarFile jarFile = jarURLConnection.getJarFile();
                        Enumeration<JarEntry> jarEntries = jarFile.entries();
                        while (jarEntries.hasMoreElements()) {
                            JarEntry jarEntry = jarEntries.nextElement();
                            String jarEntryName = jarEntry.getName();
                            if (jarEntryName.endsWith(".class")) {
                                String className = jarEntryName.substring(0, jarEntryName.lastIndexOf(".")).replaceAll("/", ".");
                               CtClass ctclz = pool.getCtClass(className);
                               if(ctclz.getAnnotation(Table.class) != null) {
                            	   Class<?> cls = Class.forName(className);
                            	   classes.put(cls.getName(), cls);
                               }
//                                Class<?> cls = Class.forName(className);
//                                if (cls.isAnnotationPresent(annotationClass)) {
//                                    classes.put(cls.getName(), cls);
//                                }
                            }
                        }
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return classes;
    }
 
    private static void addClass(Map<String,Class<?>> classes, String packagePath, String packageName, boolean isRecursive) {
        try {
            File[] files = getClassFiles(packagePath);
            if (files != null) {
                for (File file : files) {
                    String fileName = file.getName();
                    if (file.isFile()) {
                        String className = getClassName(packageName, fileName);
                        Class<?> clz = Class.forName(className);
                        classes.put(clz.getName(), clz);
                    } else {
                        if (isRecursive) {
                            String subPackagePath = getSubPackagePath(packagePath, fileName);
                            String subPackageName = getSubPackageName(packageName, fileName);
                            addClass(classes, subPackagePath, subPackageName, isRecursive);
                        }
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
 
    private static File[] getClassFiles(String packagePath) {
        return new File(packagePath).listFiles(new FileFilter() {
            @Override
            public boolean accept(File file) {
                return (file.isFile() && file.getName().endsWith(".class")) || file.isDirectory();
            }
        });
    }
 
    private static String getClassName(String packageName, String fileName) {
        String className = fileName.substring(0, fileName.lastIndexOf("."));
        if (!StringUtils.isNullOrEmpty(packageName)) {
            className = packageName + "." + className;
        }
        return className;
    }
 
    private static String getSubPackagePath(String packagePath, String filePath) {
        String subPackagePath = filePath;
        if (!StringUtils.isNullOrEmpty(packagePath)) {
            subPackagePath = packagePath + "/" + subPackagePath;
        }
        return subPackagePath;
    }
 
    private static String getSubPackageName(String packageName, String filePath) {
        String subPackageName = filePath;
        if (!StringUtils.isNullOrEmpty(packageName)) {
            subPackageName = packageName + "." + subPackageName;
        }
        return subPackageName;
    }
 
    private static void addClassByAnnotation(Map<String,Class<?>> classes, String packagePath, String packageName, Class<? extends Annotation> annotationClass) {
        try {
        	ClassPool pool = ClassPool.getDefault();
            File[] files = getClassFiles(packagePath);
            if (files != null) {
                for (File file : files) {
                    String fileName = file.getName();
                    if (file.isFile()) {
                        String className = getClassName(packageName, fileName);
                        CtClass ctclz = pool.getCtClass(className);
                        if(ctclz.getAnnotation(Table.class) != null) {
                        	 Class<?> cls = Class.forName(className);
                             classes.put(cls.getName(), cls);
                        }
                       
                    } else {
                        String subPackagePath = getSubPackagePath(packagePath, fileName);
                        String subPackageName = getSubPackageName(packageName, fileName);
                        addClassByAnnotation(classes, subPackagePath, subPackageName, annotationClass);
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    public static void main(String[] args) {
    	System.out.println(getClassesByAnnotation("com.app", Table.class));
	}
}